package com.gome.ss.core.plugin;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;

import org.apache.log4j.Logger;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;

import com.gome.ss.common.AppConfiguer;
import com.gome.ss.common.Utils;
import com.gome.ss.core.schedule.ScheduleManager;
import com.gome.ss.schedule.model.PluginModel;

public class PluginManager {
	private static Logger logger = Logger.getLogger(PluginManager.class);
	
	public final static  String START_REQUEST_METHOD_FUNCTION = "function";
	public final static  String START_REQUEST_METHOD_XML = "xml";
	public final static  String START_REQUEST_METHOD_CONFIG = "config";
	static Map<String, List<Plugin>> pluginMap = new HashMap<String, List<Plugin>>();
	static Map<String, String> pluginNameMap = new HashMap<String, String>();
	
	static PluginManager instance = null;
	
	private PluginManager () {
		
	}
	
	static public PluginManager getInstance() {
		if ( instance != null )
			return instance;
		
		 synchronized (PluginManager.class) {
			 if ( instance == null ) {
				 instance = new PluginManager();
				 instance.lookingForPlugin();
			 }
		 }
		return instance;
	}
	
	/**
	 * 加载插件，如果加载中发现错误或者发生异常则放弃加载。
	 * @param pluginFileName
	 * @param cover
	 * @throws Exception
	 */
	private void loadPlugin(String pluginFileName, boolean cover) throws Exception {
		try {
			List<Plugin> plugins = new ArrayList<Plugin>();
	
	        JarFile jarfile = new JarFile(AppConfiguer.getPluginDir() + pluginFileName);
	        ZipEntry entry = jarfile.getEntry("plugin.xml");
	        if (entry == null)
	        	entry = jarfile.getEntry("config/plugin.xml");
	        
	        if (entry == null) {
	        	logger.error(pluginFileName + " load error!");
				File file = new File(AppConfiguer.getPluginDir() + pluginFileName);
				if (file.exists()) {
					file.delete();
					return;
				}
	        }
	        
	        InputStream is = jarfile.getInputStream(entry);
	        
	        SAXBuilder builder = new SAXBuilder();
			Document doc = builder.build(is);
			Element rootElement = doc.getRootElement();
			String pluginName = rootElement.getAttributeValue("name");
			
			String oldPluginName = getPluginName(pluginFileName);
			if (oldPluginName != null) {
				if (!cover)
					return;
				
				List<Plugin> oldPlugins = pluginMap.get(oldPluginName);
				for (Plugin plugin : oldPlugins) {
					ScheduleManager.getInstance().getScheduleCore(plugin.getHandle().getPluginQualifiedName()).destory();
					ScheduleManager.getInstance().removeScheduleCore(plugin.getHandle().getPluginQualifiedName());
					plugin.getHandle().destory();
				}
				
				pluginMap.remove(oldPluginName);
				pluginNameMap.remove(oldPluginName);
			}
			
			List<Element> pluginElements = rootElement.getChildren("plugin");
			for (Element pluginElement : pluginElements) {
				SpiderPluginHandle handle = new SpiderPluginHandle();
				Element pluginQualifiedElement = pluginElement.getChild("qualified");
				
				String pluginQualified = pluginQualifiedElement.getText();
				
				boolean processable = Boolean.valueOf(pluginElement.getAttributeValue("processable"));
				
				int maxThreadCount = 10;
				if (processable) {
					String maxThreadCountString = pluginElement.getAttributeValue("maxThreadCount");
					if (maxThreadCountString != null) {
						try {
							maxThreadCount = Integer.valueOf(maxThreadCountString);
						} catch (Exception e) {
							maxThreadCount= 10;
						}
					}
				}
				
				handle.loadClazz(pluginFileName, pluginName, pluginQualified, processable);
				
				String pluginDesc = pluginElement.getChildText("desc");
				if (pluginDesc != null)
					handle.setPluginDesc(pluginDesc);
				
				Element startRequestElement = pluginElement.getChild("startRequest");
				
				String startMethod = startRequestElement.getAttributeValue("method");
				
				if (!startMethod.equals(START_REQUEST_METHOD_FUNCTION) && !startMethod.endsWith(START_REQUEST_METHOD_XML)) {
					// start request method error !
					return;
				}
				
				handle.setStartRequestMethod(startMethod);
				
				if (startMethod.endsWith(START_REQUEST_METHOD_XML)) {
					List<Element> startUrlElements = startRequestElement.getChildren("url");
					for (Element startUrlElement : startUrlElements) {
						String link = startUrlElement.getText();
						String groupName = startUrlElement.getAttributeValue("groupName");
						String retryCountStr = startUrlElement.getAttributeValue("retryCount");
						String qualifiedString = startUrlElement.getAttributeValue("qualified");
						int retryCount = 0;
						if (!retryCountStr.isEmpty() && retryCountStr != null)
							retryCount = Integer.valueOf(retryCountStr);
						if (link.isEmpty() || link == null || groupName.isEmpty() || groupName == null)
							continue;
						
						handle.addStartReq(link, groupName, retryCount, qualifiedString);
					}
				}
				
				if (!handle.init()) {
					return;
				}
				
				Plugin plugin = new Plugin(handle);
				plugin.setMaxThreadCount(maxThreadCount);
				plugins.add(plugin);
			}
			
			logger.info("Load plugin " + pluginName + " success!");
			
			for (Plugin plugin : plugins) {
				ScheduleManager.getInstance().createScheduleCore(plugin.getHandle().getPluginQualified(), plugin.getMaxThreadCount(), -1);
			}
			
			pluginMap.put(pluginName, plugins);
			pluginNameMap.put(pluginName, pluginFileName);	
		} catch ( Exception e ) {
			logger.error("Load plugin " + pluginFileName + " error!");
			throw e;
		}
	}
	
	private void lookingForPlugin() {
		pluginMap.clear();
		pluginNameMap.clear();
		
		File dir = new File(AppConfiguer.getPluginDir());
		if (!dir.exists())
			dir.mkdir();
		
		File[] files = dir.listFiles();
		for ( File pluginFile : files ) {
			if ( pluginFile.isFile() ) {
				String pluginFileName = pluginFile.getName();

				if ( ! pluginFileName.endsWith(".jar") )
					continue;
			
				try {
					loadPlugin(pluginFileName, false);
				} catch (Exception e) {
					// TODO Auto-generated catch block
					logger.error(Utils.getTrace(e));
				}
			}
		}
	}
	
	public void loadPluginFromJar(String jarFileName, boolean cover) {
		if (jarFileName == null || jarFileName.isEmpty())
			return;
		
		File file = new File(AppConfiguer.getPluginDir()+jarFileName);
		
		if (!file.exists())
			return;
		
		if (!jarFileName.endsWith(".jar"))
			return;
	
		try {
			loadPlugin(jarFileName, cover);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			logger.error(Utils.getTrace(e));
		}
	}
	
	public Plugin getPlugin(String pluginName, String qualified) {
		List<Plugin> plugins = pluginMap.get(pluginName);
		for ( Plugin plugin : plugins ) {
			if (plugin.isLocalPlugin()) {
				SpiderLocalPluginHandle handle = (SpiderLocalPluginHandle) plugin.getHandle();
				if ( handle.getConfig().getQualifiedName().equals(qualified) )
					return plugin;
			} else {
				if ( plugin.getHandle().getPluginQualified().equals(qualified) )
					return plugin;
			}
		}
		return null;
	}
	
	public List<Plugin> getPlugin(String pluginName) {
		return pluginMap.get(pluginName);
	}
	
	public PluginModel getPluginModel(String pluginName, String qualified) {
		List<Plugin> plugins = pluginMap.get(pluginName);
		for ( Plugin plugin : plugins ) {
			if (plugin.isLocalPlugin()) {
				SpiderLocalPluginHandle handle = (SpiderLocalPluginHandle) plugin.getHandle();
				if ( handle.getConfig().getQualifiedName().equals(qualified) )
					return new PluginModel(plugin);
			} else {
				if ( plugin.getHandle().getPluginQualified().equals(qualified) )
					return new PluginModel(plugin);
			}
		}
		return null;
	}
	
	public Set<String> getPluginNameSet() {
		return pluginMap.keySet();
	}
	
	public String getPluginFileName(String pluginName) {
		if (isLocalPlugin(pluginName))
			return "Local Plugin";
		
		return pluginNameMap.get(pluginName);
	}
	
	public boolean isLocalPlugin(String pluginName) {
		List<Plugin> plugins = pluginMap.get(pluginName);
		if (plugins.size() > 0) {
			if (plugins.get(0).isLocalPlugin())
				return true;
		}
		return false;
	}
	
	public String getPluginName(String pluginFileName) {
		Set<String> pnames = pluginNameMap.keySet();
		for (String pname : pnames) {
			if (pluginNameMap.get(pname).equals(pluginFileName))
				return pname;
		}
		return null;
	}
	
	public List<PluginModel> getPluginModelByName(String pluginName) {
		List<PluginModel> pms = new ArrayList<PluginModel>();
		List<Plugin> plugins = pluginMap.get(pluginName);
		
		if (plugins != null) {
			for ( Plugin plugin : plugins )
				pms.add(new PluginModel(plugin));
		}
		
		return pms;
	}
	
	public boolean deletePlugin(String pluginName) {
		
		if (pluginMap.containsKey(pluginName)) {

			
			List<Plugin> plugins = pluginMap.get(pluginName);
			for (Plugin plugin : plugins) {
				ScheduleManager.getInstance().getScheduleCore(plugin.getHandle().getPluginQualifiedName()).destory();
				ScheduleManager.getInstance().removeScheduleCore(plugin.getHandle().getPluginQualifiedName());
				plugin.getHandle().destory();
			}
			pluginMap.remove(pluginName);
		}
		
		if (pluginNameMap.containsKey(pluginName)) {
			String fileName = pluginNameMap.get(pluginName);
			pluginNameMap.remove(pluginName);

			File file = new File(AppConfiguer.getPluginDir() + fileName);
			if (file.exists())
				return file.delete();
		}
		logger.info("Unload plugin " + pluginName + " success!");
		return true;
	}
	
	public void loadLocalPlugin(SpiderLocalPluginConfig localPluginConfig, boolean cover) throws IllegalArgumentException, SecurityException, ClassNotFoundException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, IOException, InstantiationException, JDOMException {
		String pluginName = localPluginConfig.getName();
		
		List<Plugin> plugins = new ArrayList<Plugin>();
		
		List<SpiderLocalPluginStepConfig> pluginStepConfigs = localPluginConfig.getSteps();
		for (int i = 0; i < pluginStepConfigs.size(); i ++) {

			if (i != pluginStepConfigs.size()-1)
				pluginStepConfigs.get(i).setNextStepQualifiedName(pluginStepConfigs.get(i+1).getQualifiedName());
			
			SpiderLocalPluginHandle handle = new SpiderLocalPluginHandle();			
			handle.loadLocalClazz(pluginStepConfigs.get(i), pluginName);
			handle.setConfig((Object)pluginStepConfigs.get(i));
			
			String pluginDesc = pluginStepConfigs.get(i).getDesc();
			if (pluginDesc != null)
				handle.setPluginDesc(pluginDesc);
			
			List<String> startUrls = pluginStepConfigs.get(i).getStartUrls();
			String startMethod = pluginStepConfigs.get(i).getStartMethod();
			if (!startMethod.equals(START_REQUEST_METHOD_CONFIG)) {
				// start request method error !
				return;
			}
			handle.setStartRequestMethod(startMethod);
			
			int overlapActionCount = 0;
			List<SpiderLocalPluginActionConfig> actions = pluginStepConfigs.get(i).getBaction();
			for (SpiderLocalPluginActionConfig action : actions) {
				if (action.isOverlap())
					overlapActionCount ++;
			}
			
			if (overlapActionCount == 0)
				overlapActionCount = 1;
			
			for (int x = 0; x < overlapActionCount; x ++) {
				for (String link : startUrls) {
					String groupName = pluginStepConfigs.get(i).getGroup();
					String qualifiedString = pluginStepConfigs.get(i).getQualifiedName();
					int retryCount = 3;
					
					if (link.isEmpty() || link == null || groupName.isEmpty() || groupName == null)
						continue;
					
					handle.addStartReq(link, groupName, retryCount, qualifiedString);
				}
			}
			
			if (!handle.init()) {
				return;
			}
			
			Plugin plugin = new Plugin(handle);
			plugin.setIsLocalPlugin();
			plugin.setMaxThreadCount(localPluginConfig.getMaxThreadCount());
			plugins.add(plugin);
		}
		
		logger.info("Load local plugin " + pluginName + " success!");
        
		for (Plugin plugin : plugins) {
			ScheduleManager.getInstance().createScheduleCore(plugin.getHandle().getPluginQualifiedName(),
							plugin.getMaxThreadCount(), localPluginConfig.getScheduleInterval());
		}
		
		pluginMap.put(pluginName, plugins);
	}
}
